﻿using AppUpdateInstaller.Model;
using AppUpdateInstaller.Model.InstallationStages;
using ConsoleHoster.Common.Utilities;
using ConsoleHoster.Common.Utilities.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using System.IO;
using TestUtilities;

namespace AppUpdateInstaller.UnitTests.ModelTests
{
	[TestClass]
	public class ExtractPackageStageTests
	{
		private const string archiveLocation = "archive location";
		private const string binariesFolderPath = "binaries folder path";
		private const string updateFolder = @"C:\update Folder";
		private const string randomFolderName = "randomeFolder Name";

		private Mock<IProcessManager> mockProcessManager;
		private Mock<IFileSystemContentProvider> mockFSContentProvider;
		private ActionMock mockShutdownAction;
		private ActionMock<string, string> mockExtractArchiveLogic;

		private ExtractPackageStage extractStage;

		[TestInitialize]
		public void InitializeTest()
		{
			this.mockFSContentProvider = new Mock<IFileSystemContentProvider>();
			this.mockProcessManager = new Mock<IProcessManager>();

			this.mockShutdownAction = new ActionMock();
			this.mockExtractArchiveLogic = new ActionMock<string, string>();
		}

		[TestMethod]
		public void Constructor_ChecksForArchiveFileExistance()
		{
			AssertExtensions.Throws<FileNotFoundException>(() => this.InitializeExtractStage());

			this.mockFSContentProvider.Verify(fcp => fcp.FileExists(archiveLocation), Times.Once());
		}

		[TestMethod]
		public void Constructor_ThrowsIfBinariesFolderDoesntExist()
		{
			this.mockFSContentProvider
				.Setup(fcp => fcp.FileExists(archiveLocation))
				.Returns(true);

			AssertExtensions.Throws<DirectoryNotFoundException>(() => this.InitializeExtractStage());

			this.mockFSContentProvider.Verify(fcp => fcp.DirectoryExists(binariesFolderPath), Times.Once());
		}

		[TestMethod]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Constructor_ThrowsForNullExtractArchive()
		{
			this.InitializePreconditions();

			new ExtractPackageStage(archiveLocation, binariesFolderPath, this.ProcessManager, this.FSContentProvider, this.ShutdownAction, null);
		}

		[TestMethod]
		public void HasFinalizerStage_ReturnsTrue()
		{
			this.InitializePreconditions();
			this.InitializeExtractStage();

			Assert.IsTrue(this.extractStage.HasFinalizerStage);
		}

		[TestMethod]
		public void Run_NotifiesProgressChange()
		{
			this.InitializePreconditions();
			this.InitializeExtractStage();

			IInstallationStage tmpActualStage = null;
			InstallationStageProgressEventArgs tmpActualEA = null;
			int? tmpTimesCalled = 0;

			this.extractStage.ProgressChanged += (st, ea) =>
				{
					tmpActualStage = st;
					tmpActualEA = ea;
					tmpTimesCalled++;
				};
			this.extractStage.Run();

			Assert.AreEqual(1, tmpTimesCalled.Value);
			Assert.AreEqual(this.extractStage, tmpActualStage);
			Assert.IsNotNull(tmpActualEA);
		}

		[TestMethod]
		public void Run_ExtractsArchiveFile()
		{
			this.InitializePreconditions();
			this.InitializeExtractStage();

			this.extractStage.Run();

			this.mockExtractArchiveLogic.VerifyCall(archiveLocation, Path.Combine(updateFolder, randomFolderName));
		}

		private void InitializeExtractStage()
		{
			this.extractStage = new ExtractPackageStage(archiveLocation, binariesFolderPath, this.ProcessManager, this.FSContentProvider, this.ShutdownAction, this.ExtractArchiveLogic);
		}

		private void InitializePreconditions()
		{
			this.mockFSContentProvider
				.Setup(fcp => fcp.FileExists(archiveLocation))
				.Returns(true);

			this.mockFSContentProvider
				.Setup(fcp => fcp.DirectoryExists(binariesFolderPath))
				.Returns(true);

			this.mockFSContentProvider
				.Setup(fcp => fcp.GetFileDirectory(archiveLocation))
				.Returns(updateFolder);

			this.mockFSContentProvider
				.Setup(fcp => fcp.GetRandomFolderName())
				.Returns(randomFolderName);
		}

		private IFileSystemContentProvider FSContentProvider
		{
			get
			{
				return this.mockFSContentProvider.Object;
			}
		}

		private IProcessManager ProcessManager
		{
			get
			{
				return this.mockProcessManager.Object;
			}
		}

		private Action ShutdownAction
		{
			get
			{
				return this.mockShutdownAction.Action;
			}
		}

		private Action<string, string> ExtractArchiveLogic
		{
			get
			{
				return this.mockExtractArchiveLogic.Action;
			}
		}
	}
}
